#include "scheduler.h"
#include "log.h"
#include "macro.h"

namespace sylar {
  static sylar::Logger::ptr g_logger = SYLAR_LOG_NAME("system");
  /**
   * @param t_scheduler 当前线程的调度器，同一个调度器下的所有线程指同同一个调度器实例
   * @param t_scheduler_fiber 当前线程的调度协程，每个线程都独有一份，包括caller线程
   * */
  static thread_local Scheduler* t_scheduler = nullptr;
  static thread_local Fiber* t_scheduler_fiber = nullptr;
  /**
   * @brief: sylar的协程调度器在初始化时支持传入线程数和一个布尔型的use_caller参数，
   *         表示是否使用caller线程。在使用caller线程的情况下，线程数自动减一
   *         并且调度器内部会初始化一个属于caller线程的调度协程并保存起来（比如，在main函数中创建的调度器，
   *         如果use_caller为true，那调度器会初始化一个属于main函数线程的调度协程）。
   * @detail: 如果这里全使用默认参数（即线程数为1且使用调度器的线程），那么构造函数将不会开启新的
   *          线程。添加的调度任务会在stop的时候执行而不是一加入任务就执行
   * */
  Scheduler::Scheduler(size_t threads, bool use_caller, const std::string& name)
          :m_name(name) {
    SYLAR_ASSERT(threads > 0);
    if(use_caller) {
      // 初始化一个协橙
      sylar::Fiber::GetThis();
      --threads;

      SYLAR_ASSERT(GetThis() == nullptr);
      t_scheduler = this;

      m_root_fiber.reset(new Fiber(std::bind(&Scheduler::run, this), 0, true));
      sylar::Thread::SetName(m_name);

      t_scheduler_fiber = m_root_fiber.get();
      m_rootThreadId = sylar::GetThreadId();
      m_threadIds.push_back(m_rootThreadId);
    } else {
      m_rootThreadId = -1;
    }
    m_threadCount = threads;
  }

  Scheduler::~Scheduler() {
    SYLAR_ASSERT(m_stopping);
    if(GetThis() == this) {
      t_scheduler = nullptr;
    }
  }

  Scheduler* Scheduler::GetThis() {
    return t_scheduler;
  }

  Fiber* Scheduler::GetMainFiber() {
    return t_scheduler_fiber;
  }

  void Scheduler::start() {
    MutexType::Lock lock(m_mutex);
    if(!m_stopping) {
      return;
    }
    m_stopping = false;
    SYLAR_ASSERT(m_threads.empty());

    m_threads.resize(m_threadCount);
    for(size_t i = 0; i < m_threadCount; ++i) {
      m_threads[i].reset(new Thread(std::bind(&Scheduler::run, this)
              , m_name + "_" + std::to_string(i)));
      m_threadIds.push_back(m_threads[i]->getId());
    }
    lock.unlock();

  }
  /**
   * @brief: 如果没有use_caller，则简单的等待所有线程执行完任务后退出即可
   *         否则需要让m_root_fiber再运行一次，即在stop时才执行需要执行的任务
   * */
  void Scheduler::stop() {
    m_autostop = true;
    if(m_root_fiber
       && m_threadCount == 0
       && (m_root_fiber->getState() == Fiber::TERM
           || m_root_fiber->getState() == Fiber::INIT)) {
      SYLAR_LOG_INFO(g_logger) << this << " stopped";
      m_stopping = true;

      if(stopping()) {
        return;
      }
    }

    if(m_rootThreadId != -1) {
      SYLAR_ASSERT(GetThis() == this);
    } else {
      SYLAR_ASSERT(GetThis() != this);
    }

    m_stopping = true;
    // 通知其他调度线程的调度协程退出调度
    for(size_t i = 0; i < m_threadCount; ++i) {
      tickle();
    }
    // 通知当前线程的调度协程退出调度
    if(m_root_fiber) {
      tickle();
    }

    if(m_root_fiber) {
      if(!stopping()) {
        /// use_caller情况下，使用call()先进入root调度协橙， 在调度协橙里在完成未完成的任务
        m_root_fiber->call();
      }
    }

    std::vector<Thread::ptr> thrs;
    {
      MutexType::Lock lock(m_mutex);
      thrs.swap(m_threads);
    }

    for(auto& i : thrs) {
      i->join();
    }
  }

  void Scheduler::setThis() {
    t_scheduler = this;
  }
  /**
   * @detail：如果直接是回调函数的任务，则会创建一个协橙把回调函数包起来然后再执行
   * */
  void Scheduler::run() {
    set_hook_enable(true);
    setThis();
    /// 对于线程池里的线程，不需要同时保存三个协橙（他也没有三个协橙），只需要两个协橙来回调度即可
    if(sylar::GetThreadId() != m_rootThreadId) {
      /// 为任务线程（子线程）新建协橙，并将其设置为调度协橙
      t_scheduler_fiber = Fiber::GetThis().get();
    }
    Fiber::ptr idle_fiber(new Fiber(std::bind(&Scheduler::idle, this)));
    Fiber::ptr cb_fiber;

    FiberAndThread ft;
    while(true) {
      ft.reset();
      bool tickle_me = false;
      bool is_active = false;
      {
        MutexType::Lock lock(m_mutex);
        auto it = m_fibers.begin();
        while(it != m_fibers.end()) {
          /// 指定了调度线程，但不是在当前线程上调度，标记一下需要通知其他线程进行调度，然后跳过这个任务，继续下一个
          if(it->threadId != -1 && it->threadId != sylar::GetThreadId()) {
            ++it;
            tickle_me = true;
            continue;
          }
          SYLAR_ASSERT(it->fiber || it->cb);
          if(it->fiber && it->fiber->getState() == Fiber::EXEC) {
            ++it;
            continue;
          }
          ft = *it;
          m_fibers.erase(it++);
          ++m_activeThreadCount;
          is_active = true;
          break;
        }
        // 当前线程拿完一个任务后，发现任务队列还有剩余，那么tickle一下其他线程
        tickle_me |= it != m_fibers.end();
      }
      if(tickle_me) {
        tickle();
      }
      // 如果是协橙的任务
      if(ft.fiber && (ft.fiber->getState() != Fiber::TERM
                      && ft.fiber->getState() != Fiber::EXCEPT)) {
        // 他会调用MainFunc并在里面调用回调函数
        ft.fiber->swapIn();
        --m_activeThreadCount;
        // 如果没执行完。。
        if(ft.fiber->getState() == Fiber::READY) {
          schedule(ft.fiber);
        } else if(ft.fiber->getState() != Fiber::TERM
                  && ft.fiber->getState() != Fiber::EXCEPT) {
          ft.fiber->setState(Fiber::HOLD);
        }
        ft.reset();
      } else if(ft.cb) {
        if(cb_fiber) {
          cb_fiber->reset(ft.cb);
        } else {
          cb_fiber.reset(new Fiber(ft.cb));
        }
        ft.reset();
        cb_fiber->swapIn();
        --m_activeThreadCount;
        if(cb_fiber->getState() == Fiber::READY) {
          schedule(cb_fiber);
          cb_fiber.reset();
        } else if(cb_fiber->getState() == Fiber::EXCEPT
                  || cb_fiber->getState() == Fiber::TERM) {
          cb_fiber->reset(nullptr);
        } else {
          cb_fiber->setState(Fiber::HOLD);
          cb_fiber.reset();
        }
      } else { // 如果这两都不是那说明现在正在执行idle任务
        if(is_active) {
          --m_activeThreadCount;
          continue;
        }
        if(idle_fiber->getState() == Fiber::TERM) {
          /// 如果调度器没有调度任务，那么idle协程会不停地resume/yield，不会结束
          /// 如果idle协程结束了，那一定是调度器停止了(即执行了stop())
          SYLAR_LOG_INFO(g_logger) << "idle fiber term";
          break;
        }

        ++m_idleThreadCount;
        idle_fiber->swapIn();
        --m_idleThreadCount;
        if(idle_fiber->getState() != Fiber::TERM
           && idle_fiber->getState() != Fiber::EXCEPT) {
          idle_fiber->setState(Fiber::HOLD);
        }
      }
    }
  }

  void Scheduler::tickle() {
    SYLAR_LOG_INFO(g_logger) << "tickle";
  }

  bool Scheduler::stopping() {
    MutexType::Lock lock(m_mutex);
    return m_autostop && m_stopping
           && m_fibers.empty() && m_activeThreadCount == 0;
  }

  void Scheduler::idle() {
    SYLAR_LOG_INFO(g_logger) << "idle";
    while(!stopping()) {
      sylar::Fiber::YieldToHold();
    }
  }
}